using System;
using System.Text;

namespace KodeIT.Web
{
    /// <summary>
    /// This class represents the abstract event raised by IIS. IIS provides this event for access to information
    /// about the current request and response. The HttpFilter will always have access to all methods and
    /// properties provided by this class.
    /// </summary>
    public class ContextEvent
    {
        private NativeContext _nativeContext;
        private ServerVariables _serverVariables;
        private FilterStatus _status;
        private bool _populated;
        private bool _isSecure;
        private byte[] _bytes;

        internal ContextEvent(NativeContext nativeContext)
        {
            _nativeContext = nativeContext;
            _status = FilterStatus.NextNotification;
            _serverVariables = new ServerVariables(nativeContext);
        }

        /// <summary>
        /// Gets the instance that holds all custom data associated with the current TCP session.
        /// </summary>
        public FilterSession Session
        {
            get { return _nativeContext.Session; }
        }

        internal NativeContext NativeContext
        {
            get { return _nativeContext; }
        }

        /// <summary>
        /// Gets the collection of the <see cref="ServerVariables"/> associated with the current request.
        /// </summary>
        public ServerVariables ServerVariables
        {
            get { return _serverVariables; }
        }

        /// <summary>
        /// Gets a value indicating whether the request is from the local computer.
        /// </summary>
        /// <remarks>
        /// The <see cref="IsLocal"/> property returns true if the IP address of the request originator is 127.0.0.1 or if the IP address of the request is the same as the server's IP address.
        /// </remarks>
        public bool IsLocal
        {
            get
            {
                string remoteAddress = ServerVariables[ServerVariable.REMOTE_ADDR];
                return
                    !String.IsNullOrEmpty(remoteAddress) &&
                    ((remoteAddress == "127.0.0.1") ||
                     (remoteAddress == "::1") ||
                     (remoteAddress == ServerVariables[ServerVariable.LOCAL_ADDR]));
            }
        }

        /// <summary>
        /// Gets a boolean value indicating whether the event is over a secure port or not over a secure port.
        /// </summary>
        public bool IsSecureConnection
        {
            get
            {
                if (!_populated)
                {
                    _populated = true;
                    _isSecure = NativeMethods.IsSecure(_nativeContext);
                }

                return _isSecure;
            }
        }

        /// <summary>
        /// Sends an array of bytes to the client.
        /// </summary>
        /// <param name="data">The data to send to the client.</param>
        /// <remarks>
        /// When calling this method, it usually indicates the end of the request, and therefore a call
        /// to this method is expected to be followed by a call to <see cref="TerminateRequest"/>
        /// </remarks>
        public void WriteClient(byte[] data)
        {
            if (null == data)
            {
                throw new ArgumentNullException("data",
                    "The data cannot be null");
            }

            int offset = 0;

            if (null == _bytes)
            {
                _bytes = new byte[data.Length];
            }
            else
            {
                offset = _bytes.Length;
                byte[] temp = new byte[_bytes.Length + data.Length];
                Array.Copy(_bytes, temp, _bytes.Length);
                _bytes = temp;
            }

            Array.Copy(data, 0, _bytes, offset, data.Length);
        }

        /// <summary>
        /// Terminates the current request with the option to disconnect the session. Upon calling
        /// this method, the next event to be raised, if subscribed, is the <see cref="IFilterEvents.EndOfNetSession"/>
        /// event.
        /// </summary>
        /// <param name="keepConnection">Notifies if the TCP session, if negotiated, should be kept open or not.</param>
        public void TerminateRequest(bool keepConnection)
        {
            _status = keepConnection ? FilterStatus.FinishedKeepConn : FilterStatus.Finished;
        }

        internal virtual FilterStatus Update()
        {
            if (null != _bytes)
            {
                NativeMethods.WriteClient(_nativeContext, _bytes);
            }

            return _status;
        }

        internal FilterStatus ProcessError(ContextEvent context, FilterEvent filterEvent, Type type, Exception exp)
        {
            EventHelper.WriteInstance(filterEvent, type, exp);
            return ProcessErrorImpl(context, filterEvent, type, exp);
        }

        internal FilterStatus ProcessError(ContextEvent context, FilterEvent filterEvent, Exception exp)
        {
            EventHelper.WriteEvent(filterEvent, exp);
            return ProcessErrorImpl(context, filterEvent, null, exp);
        }

        private FilterStatus ProcessErrorImpl(ContextEvent context, FilterEvent filterEvent, Type type, Exception exp)
        {
            string payload = ErrorPage.GetPayload(context, filterEvent, type, exp);
            string headers = String.Format(
                "HTTP/1.1 200 OK\r\n" +
                "Date: {0}\r\n" +
                "Content-Length: {1}\r\n" +
                "Content-Type: text/html\r\n" +
                "Connection: Close\r\n" +
                "Server: {2}\r\n\r\n",
                DateTime.Now.ToString("d MMM yyyy hh:mm:ss GMT"),
                payload.Length,
                ServerVariables[ServerVariable.SERVER_SOFTWARE]);

            NativeMethods.WriteClient(_nativeContext, ASCIIEncoding.ASCII.GetBytes(headers + payload));
            return FilterStatus.Finished;
        }
    }
}
